
#include "scheme.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>


//These are the token types
enum {token, number, string, symbol, node, eot};


typedef struct 
{
  unsigned int type;
  union
  {
    Utf8_string str;
    long int num;
    double dbl;
    char tok;
  } val;
} YJK_Token;

struct atom
{
  unsigned int type;
  union
  {
    Utf8_string str;
    long int num;
    double dbl;
    struct ast_node *node;
  } data;
};

struct ast_node
{
  struct atom car;
  struct atom cdr;
};

typedef struct ast_node AST_Node;


static int
getNumber(Utf8_string text, Utf8_string *stop, YJK_Token *t)
{
  char *c = text;
  char *endptr = NULL;
 
  errno = 0;
  long int a = strtol(c, &endptr, 0);
  if (errno)
    {
      return 0;
    }
  
  t->type = number;
  t->val.num = a;

  *stop = endptr;
  return 1;
}


static int
getString(Utf8_string text, Utf8_string *stop, YJK_Token *t)
{
  char *c = text + 1; //skip initial double quote
  Utf8_string str = NULL;

  while (*c)
    {
      switch(*c++)
	{
	case '"':
	  str = calloc(c - text, 1);
	  strncpy(str, text + 1, (c - text) - 2); //skip first and last double quote
	  t->type = string;
	  t->val.str = str;
	  *stop = c;
	  return 1;
	case '\\':
	  c++;
	  break;
	case 0:
	  printf("Error: Unterminated string.\n");
	  return 0;
	default:
	  break;
	}
    }
  return 0;
}

static int
getSymbol(Utf8_string text, Utf8_string *stop, YJK_Token *t)
{
  Utf8_string c = text;
  Utf8_string str = NULL;

  while (' ' != *c && '\t' != *c && '\n' != *c &&
	 '(' != *c && ')' != *c)
    {
      c++;
    }

  str = calloc((c - text) + 1, 1);
  strncpy(str, text, (c - text)); //skip the last character
  t->type = symbol;
  t->val.str = str;
  *stop = c; //put the last character back on stream
  return 1;
}


static int
getToken(Utf8_string text, Utf8_string *stop, YJK_Token *t)
{
  char *c = text;
  
  while (' ' == *c || '\t' == *c || '\n' == *c || 
	 '[' == *c || ']' == *c || 
	 '{' == *c || '}' == *c)
    {
      c++;
    }
  
  switch (*c)
    {
    case 0:
      t->type = eot;
      t->val.num = 0;
      *stop = c;
      return 0;
    case '(':
    case ')':
      t->type = token;
      t->val.tok = *c;
      *stop = c + 1;
      return 1;
    case '"':
      return getString(c, stop, t);
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
    case '-':
    case '.':
      return getNumber(c, stop, t);

    default:
      return getSymbol(c, stop, t);
    }

  return 0;
}


static AST_Node *
make_ast(Utf8_string text, Utf8_string *stop)
{
  YJK_Token t;
  Utf8_string s = NULL;
  AST_Node *n = NULL;

  if (getToken(text, &s, &t))
    {
      switch(t.type)
	{
	case token:
	  printf("Token  (%d): %c\n", t.type, t.val.tok);
	  switch (t.val.tok)
	    {
	    case '(':
	      n = malloc(sizeof(AST_Node));
	      n->car.type = node;
	      n->car.data.node = make_ast(s, &s);
	      n->cdr.type = node;
	      n->cdr.data.node = make_ast(s, &s);
	      *stop = s;
	      return n;
	      
	    case ')': 
	      *stop = s;
	      return NULL;
		
	    default:
	      break;
	    }
	  
	case number:
	  printf("Number (%d): %ld\n", t.type, t.val.num);
	  n = malloc(sizeof(AST_Node));
	  n->car.type = number;
	  n->car.data.num = t.val.num;
	  n->cdr.type = node;
	  n->cdr.data.node = make_ast(s, &s);
	  *stop = s;
	  return n;

	case string:
	  printf("String (%d): %s\n", t.type, t.val.str);
	  n = malloc(sizeof(AST_Node));
	  n->car.type = string;
	  n->car.data.str = t.val.str;
	  n->cdr.type = node;
	  n->cdr.data.node = make_ast(s, &s);
	  *stop = s;
	  return n;

	case symbol:
	  printf("Symbol (%d): %s\n", t.type, t.val.str);
	  n = malloc(sizeof(AST_Node));
	  n->car.type = symbol;
	  n->car.data.str = t.val.str;
	  n->cdr.type = node;
	  n->cdr.data.node = make_ast(s, &s);
	  *stop = s;
	  return n;

	default:
	  break;
	}
    }

  *stop = s;
  return NULL;
}


static void
print_cell(struct atom *c)
{
  if (NULL == c) 
    return;
  switch (c->type)
    {
    case number:
      printf(" %ld ", c->data.num);
      break;
    case string:
      printf(" \"%s\" ", c->data.str);
      break;
    case symbol:
      printf(" %s ", c->data.str);
      break;
    }
}


static void
print_node(AST_Node *root, int indent)
{
  if (NULL == root)
    {
      return;
    }

  //print car
  if (node == root->car.type)
    {
      int i = 0;
      printf("\n");
      for(i = 0; i < indent; i++) printf(" ");
      printf("(");
      print_node(root->car.data.node, indent + 2);
    }
  else
    {
      print_cell(&root->car);
    }

  //print cdr
  if (node == root->cdr.type)
    {
      if (NULL == root->cdr.data.node)
	{
	  printf(")");
	}
      else
	{
	  print_node(root->cdr.data.node, indent);
	}
    }
}


void
yjk_print_ast(AST_Node *root)
{
  printf("(progn\n");
  print_node(root, 2);
  printf("\n");
}

static void
compile_ast(AST_Node *root);

void
yjk_scheme_parse(Utf8_string text)
{
  AST_Node *root = NULL;
  Utf8_string stop = NULL;
  root = make_ast(text, &stop);
  yjk_print_ast(root);
  
  compile_ast(root);
}



static int
is_pair(struct atom *c)
{
  if (node == c->type) return 1;
  return 0;
}

static int
is_symbol(struct atom *c)
{
  if (symbol == c->type) return 1;
  return 0;
}


static void
compile_arglist(AST_Node *n)
{
  int first = 1;
  while (symbol == n->car.type)
    {
      if (first) { first = 0; }
      else { printf(", "); }
      
      printf("%s", n->car.data.str);

      if (node != n->cdr.type || NULL == n->cdr.data.node)
	{
	  break;
	}
      n = n->cdr.data.node;
    }
  printf(")\n");
  return;
}


//At this point, car should be arg list, and cadr should be func def
static void 
compile_define(AST_Node *n)
{
  struct atom *car = &n->car;
  struct atom *cdr = &n->cdr;
  
  if (!is_pair(car))
    {
      printf("ERROR: Argument list expected.");
    }
  else
    {
      printf("%s(", car->data.node->car.data.str);
      //FIXME: make sure cdr is a node
      compile_arglist(car->data.node->cdr.data.node);
    }

}


static void
compile_sexpr(AST_Node *n)
{
  struct atom *car = &n->car;
  struct atom *cdr = &n->cdr;

  if (!strcmp(car->data.str, "define"))
    {
      printf("function ");
      if (node != cdr->type || !is_pair(cdr))
	{
	  printf ("ERROR: Argument list expected");
	}
      else
	{
	  compile_define(cdr->data.node);
	}
    }
}


static void
compile_ast(AST_Node *root)
{
  struct atom *n;
  if (NULL == root) return;
  
  n = &root->car;
  if (is_pair(n))
    compile_sexpr(n->data.node);

  if (node == root->cdr.type)
    compile_ast(root->cdr.data.node);

}
